﻿using Padsurface.App.Services.Interfaces;
using Padsurface.Audio.Engine.Interfaces;
using Padsurface.Audio.Song.Model;
using Padsurface.Audio.Song.Services.Interfaces;
using Padsurface.Audio.Wave;
using System;
using System.Collections.ObjectModel;
using System.Composition;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.ApplicationModel.Core;
using Windows.Storage;
using Windows.UI.Core;

namespace Padsurface.App.ViewModels
{
    [Export]
    public partial class PadViewModel : BindableBase
    {
        private readonly IFilePicker _filePicker;
        private readonly WaveFileInput _waveFileInput;
        private readonly ISongSaver _songSaver;

        private readonly TimeSpan _timerUpdateInterval;
        private readonly object _lock;
        private Song _song;

        public ObservableCollection<SoundViewModel> Sounds { get; private set; }

        private double _currentBeat;
        public double CurrentBeat 
        { 
            get { return _currentBeat; }
            set { SetProperty(ref _currentBeat, value); }
        }

        [ImportingConstructor]
        public PadViewModel(IMetronome metronome, IPlayBuffer playBuffer, IFilePicker filePicker,
            WaveFileInput waveFileInput, ISongSaver songSaver)
        {
            if (metronome == null) throw new ArgumentNullException("metronome");
            if (playBuffer == null) throw new ArgumentNullException("playBuffer");
            if (filePicker == null) throw new ArgumentNullException("filePicker");
            if (waveFileInput == null) throw new ArgumentNullException("waveFileInput");
            if (songSaver == null) throw new ArgumentNullException("songSaver");

            _song = new Song(metronome, playBuffer);
            _song.SongStopped += SongStopped;
            _song.OnBeat += OnBeat;

            _filePicker = filePicker;
            _waveFileInput = waveFileInput;
            _songSaver = songSaver;

            _timerUpdateInterval = TimeSpan.FromMilliseconds(250);
            _lock = new object();

            Sounds = new ObservableCollection<SoundViewModel>();
            
            AddSoundCommand = new DelegateCommand(AddSound);
            IncreaseBpmCommand = new DelegateCommand(IncreaseBpm, CanIncreaseBpm);
            DecreaseBpmCommand = new DelegateCommand(DecreaseBpm, CanDecreaseBpm);
            SaveSongCommand = new DelegateCommand(SaveSong);
        }

        private async void OnBeat(object sender, double e)
        {
            await CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                CurrentBeat = Math.Round((double)e, 1);
            });
        }

        #region AddSoundCommand

        public ICommand AddSoundCommand { get; set; }

        private async void AddSound(object obj)
        {
            var waveFiles = await _filePicker.OpenMultipleWaveFilesAsync();

            var isCancelled = (waveFiles == null);
            if (isCancelled)
                return;

            foreach (var waveFile in waveFiles)
            {
                AddSound(waveFile);  
            }
        }

        #endregion

        private void AddSound(StorageFile waveFile)
        {
            var waveFileOutput = new WaveFileOutput(waveFile);
            var sound = new Sound(waveFileOutput);;

            _song.Sounds.Add(sound);
            Sounds.Add(new SoundViewModel(sound, _song));  //TODO: avoid manual sync of model and viewmodel
        }

        private void UpdateTimer(Action updateTimer, CancellationToken cancellationToken)
        {
            Task.Factory.StartNew(async () =>
            {
                while (!cancellationToken.IsCancellationRequested)
                {
                    var updateTimerTask = CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
                    {
                        updateTimer();
                    });

                    await Task.Delay(_timerUpdateInterval);
                }
            });
        }
    }
}